package com.autonavi.yunda.yunji.core.service;

import com.autonavi.yunda.yunji.common.exception.AmapPreconditions;
import com.autonavi.yunda.yunji.common.utils.JsonUtils;
import com.autonavi.yunda.yunji.common.utils.http.HttpClient;
import com.autonavi.yunda.yunji.common.utils.http.vo.HttpResponse;
import com.autonavi.yunda.yunji.core.enums.FunctionStatus;
import com.autonavi.yunda.yunji.core.enums.InterfaceStatus;
import com.autonavi.yunda.yunji.core.model.condition.FunctionQueryCondition;
import com.autonavi.yunda.yunji.core.model.dto.FunctionConfigDTO;
import com.autonavi.yunda.yunji.core.model.mappers.FunctionConfigMapper;
import com.autonavi.yunda.yunji.core.vo.EngineFunctionConfigVO;
import com.autonavi.yunda.yunji.core.vo.response.FunctionConfigVO;
import com.autonavi.yunda.yunji.core.vo.response.FunctionGroupVO;
import com.autonavi.yunda.yunji.core.vo.response.FunctionPageVO;
import com.autonavi.yunda.yunji.core.vo.response.SimpleFunctionConfigVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.MDC;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.autonavi.yunda.yunji.common.constant.CommonConstant.HEADER_TRACE_ID;
import static com.autonavi.yunda.yunji.common.constant.CommonConstant.MDC_TRACE_ID;
import static com.autonavi.yunda.yunji.core.config.EngineConstant.FUNCTION_VERSION_SEPARATOR;

/**
 * @author cizhong.wcs
 * @date 2022/3/7 7:37 下午
 */
@Slf4j
@Component
public class FunctionConfigService implements IFunctionConfigService {
    @Autowired
    FunctionConfigMapper functionConfigMapper;
    @Autowired(required = false)
    IIpService ipService;
    static HttpClient httpClient = new HttpClient();

    @Override
    public List<Pair<String, String>> listNewPublishedFunction(List<Pair<String, String>> existsFunctionNameVersion) {
        QueryWrapper<FunctionConfigDTO> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "name", "version");
        queryWrapper.eq("status", InterfaceStatus.PUBLISHED);
        if (!existsFunctionNameVersion.isEmpty()) {
            List<String> keyList = existsFunctionNameVersion.stream()
                    .map(p -> p.getLeft() + FUNCTION_VERSION_SEPARATOR + p.getRight()).collect(Collectors.toList());
            queryWrapper.notIn("CONCAT(name,'" + FUNCTION_VERSION_SEPARATOR + "', version)", keyList);
        }
        List<Pair<String, String>> collect = list(queryWrapper).stream().map(dto -> Pair.of(dto.getName(), dto.getVersion())).collect(Collectors.toList());
        return collect;
    }

    @Override
    public EngineFunctionConfigVO findEngineFunctionConfigVO(String functionName, String functionVersion) {
        FunctionConfigDTO dto = findByNameVersionOrThrow(functionName, functionVersion);
        EngineFunctionConfigVO vo = new EngineFunctionConfigVO();
        BeanUtils.copyProperties(dto, vo);
        return vo;
    }


    public int add(FunctionConfigDTO functionDTO) {
        List<FunctionConfigVO> sameNameVersionList = list(FunctionQueryCondition.builder()
                .name(functionDTO.getName())
                .version(functionDTO.getVersion()).build());
        AmapPreconditions.checkArgumentForUser(CollectionUtils.isEmpty(sameNameVersionList), "已存在该名称版本的方法");
        return functionConfigMapper.insert(functionDTO);
    }

    public void update(FunctionConfigDTO functionDTO) {
        functionConfigMapper.updateById(functionDTO);
    }

    public FunctionConfigDTO findByIdOrThrow(Long id) {
        FunctionConfigDTO dto = functionConfigMapper.selectById(id);
        AmapPreconditions.checkArgumentForUser(dto != null, "不存在该方法配置 id = " + id);
        return dto;
    }


    public FunctionConfigDTO findByNameVersionOrThrow(String name, String version) {
        QueryWrapper<FunctionConfigDTO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", name);
        queryWrapper.eq("version", version);
        FunctionConfigDTO dto = functionConfigMapper.selectOne(queryWrapper);
        AmapPreconditions.checkArgumentForUser(dto != null, "不存在该方法 name = " + name + ",version = " + version);
        return dto;
    }

    public List<FunctionConfigVO> list(FunctionQueryCondition condition) {
        if (condition.getIds() != null && condition.getIds().isEmpty()) {
            return new ArrayList<>();
        }
        LambdaQueryWrapper<FunctionConfigDTO> queryWrapper = condition.toQueryWrapper();
        queryWrapper.orderByDesc(FunctionConfigDTO::getId);
        List<FunctionConfigDTO> dtoList = functionConfigMapper.selectList(queryWrapper);
        return dtoList.stream().map(FunctionConfigVO::fromDto).collect(Collectors.toList());
    }

    public List<FunctionConfigDTO> list(QueryWrapper<FunctionConfigDTO> wrapper) {
        return functionConfigMapper.selectList(wrapper);
    }

    public FunctionGroupVO listFunctionGroup(FunctionQueryCondition condition) {
        if (condition.getIds() != null && condition.getIds().isEmpty()) {
            return FunctionGroupVO.empty();
        }
        Page<String> pageRes = functionConfigMapper.listDistinctFunctionName(new Page<>(condition.pageNo, condition.pageSize), condition);
        if (pageRes.getRecords().isEmpty()) {
            return new FunctionGroupVO(new ArrayList<>(), pageRes.getTotal());
        }
        condition.setNames(pageRes.getRecords());
        List<FunctionConfigVO> simpleFunctionConfigVOList = list(condition);
        Map<String, List<FunctionConfigVO>> functionNameListMap = new LinkedHashMap<>();
        for (FunctionConfigVO vo : simpleFunctionConfigVOList) {
            if (functionNameListMap.get(vo.getName()) != null) {
                functionNameListMap.get(vo.getName()).add(vo);
            } else {
                List<FunctionConfigVO> subVoList = new ArrayList<>();
                subVoList.add(vo);
                functionNameListMap.put(vo.getName(), subVoList);
            }
        }
        ArrayList<List<FunctionConfigVO>> functionGroup = new ArrayList<>(functionNameListMap.values());
        return new FunctionGroupVO(functionGroup, pageRes.getTotal());
    }

    public List<SimpleFunctionConfigVO> listSimpleFunctionVO(FunctionQueryCondition condition) {
        if (condition.getIds() != null && condition.getIds().isEmpty()) {
            return new ArrayList<>();
        }
        LambdaQueryWrapper<FunctionConfigDTO> queryWrapper = condition.toQueryWrapper();
        queryWrapper.select(
                FunctionConfigDTO::getId,
                FunctionConfigDTO::getName,
                FunctionConfigDTO::getVersion,
                FunctionConfigDTO::getDescription,
                FunctionConfigDTO::getType,
                FunctionConfigDTO::getParams,
                FunctionConfigDTO::getStatus
        );
        queryWrapper.orderByDesc(FunctionConfigDTO::getId);
        List<FunctionConfigDTO> functionConfigDTOList = functionConfigMapper.selectList(queryWrapper);
        return functionConfigDTOList.stream().map(SimpleFunctionConfigVO::from).collect(Collectors.toList());
    }

    public FunctionPageVO listPage(FunctionQueryCondition condition) {
        Page<FunctionConfigDTO> page = new Page<>(condition.getPageNo(), condition.getPageSize());
        functionConfigMapper.selectPage(page, getFunctionWrapper(condition));
        return new FunctionPageVO(page.getRecords(), page.getTotal());
    }

    private QueryWrapper<FunctionConfigDTO> getFunctionWrapper(FunctionQueryCondition condition) {
        QueryWrapper<FunctionConfigDTO> wrapper = new QueryWrapper<>();
        wrapper.orderByDesc("id");
        if (null != condition.getStatus()) {
            wrapper.eq("status", condition.getStatus());
        }
        if (StringUtils.isNotBlank(condition.getName())) {
            wrapper.eq("name", condition.getName());
        }
        if (StringUtils.isNotBlank(condition.getDescription())) {
            wrapper.like("description", String.join("%", condition.getDescription(), "%"));
        }
        if (condition.getType() != null) {
            wrapper.eq("type", condition.getType().name());
        }
        if (!CollectionUtils.isEmpty(condition.getIds())) {
            wrapper.in("id", condition.getIds());
        }
        return wrapper;
    }

    public void updateStatus(Long id, InterfaceStatus status) {
        UpdateWrapper<FunctionConfigDTO> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("id", id);
        updateWrapper.set("status", status);
        functionConfigMapper.update(null, updateWrapper);
    }

    public void notifyEngine(Long id) {
        try {
            List<String> ipList = ipService.fetchEngineIpList();
            for (String ip : ipList) {
                try {
                    HttpResponse res = httpClient.get(
                            "http://" + ip + "/_refreshFunctionCache?id=" + id,
                            Map.of(HEADER_TRACE_ID, MDC.get(MDC_TRACE_ID) == null ? "" : MDC.get(MDC_TRACE_ID))
                    );
                    log.info("notifyEngine ip =  {},res = {}", ip, JsonUtils.toString(res));
                } catch (Exception e) {
                    log.error("notifyEngine error, ip =  {}", ip, e);
                }
            }
        } catch (Exception e) {
            log.error("notifyEngine error id = {}", id, e);
        }
    }


    public void offline(Long id) {
        FunctionConfigDTO dto = findByIdOrThrow(id);
        AmapPreconditions.checkArgumentForUser(dto.getStatus().equals(FunctionStatus.PUBLISHED), "发布中的函数才能下线");
        updateStatus(id, InterfaceStatus.OFFLINE);
    }

    public void publish(Long id) {
        FunctionConfigDTO dto = findByIdOrThrow(id);
        AmapPreconditions.checkArgumentForUser(!dto.getStatus().equals(FunctionStatus.PUBLISHED), "该函数版本已发布过,无需再发布");
        updateStatus(id, InterfaceStatus.PUBLISHED);
    }

    public void delete(Long id) {
        FunctionConfigDTO dto = findByIdOrThrow(id);
        AmapPreconditions.checkArgumentForUser(dto.getStatus().equals(FunctionStatus.OFFLINE), "下线的函数才能删除");
        functionConfigMapper.deleteById(id);
    }
}
